package cz.cuni.amis.episodic.bayes.experiment;

import static cz.cuni.amis.episodic.bayes.utils.MultiexperimentSummary.createGraphDataset;
import static cz.cuni.amis.episodic.bayes.utils.MultiexperimentSummary.loadMultiExperimentResultData;
import static cz.cuni.amis.episodic.bayes.utils.MultiexperimentSummary.outputTrainingDataInfluenceGraph;

import java.io.File;
import java.io.PrintStream;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.math.MathException;
import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
import org.apache.commons.math.stat.inference.TTest;
import org.apache.commons.math.stat.inference.TTestImpl;
import org.apache.commons.math.stat.inference.TestUtils;
import org.jfree.data.xy.YIntervalSeriesCollection;

import com.itextpdf.text.pdf.PdfStructTreeController.returnType;

import cz.cuni.amis.episodic.bayes.memories.eval.TimeStats;
import cz.cuni.amis.episodic.bayes.utils.chart.GraphPaintingDevice;
import cz.cuni.amis.episodic.bayes.utils.chart.PdfGraphDevice;
import cz.cuni.amis.episodic.bayes.utils.chart.SeriesApperanceStrategy;

/**
 * ICCM 2013 experiment.
 * 
 * @author ik
 */
public class Experiment_4c_monroe_effectOfTrainingDataLarge extends
		Experiment_4c_monroe_effectOfTrainingData {

	public static String expsRootFolder = "target/experiments/4_monroe_effectOfTrainingDataLarge_2";

	static class ApperanceModels extends SeriesApperanceStrategy {

		public boolean latex = false;

		public ApperanceModels(boolean latex) {
			this.latex = latex;
		}

		public ApperanceModels() {
		}

		@Override
		public String labelFor(String architecture, String creationStrategy,
				int level, int mems) {
			// TODO write more generic code
			String label = "";
			if (architecture.contains("chmm"))
				label = "CHMM";
			else if (architecture.contains("ahmem"))
				label = "AHMEM";

			// UNICODE chars in graphs
			// http://stackoverflow.com/questions/6657538/superscript-in-jfree-chart
			if (architecture.contains("^1"))
				label += latex ? "^1" : "\u00B9";
			else if (architecture.contains("^2"))
				label += latex ? "^2" : "\u00B2";

			// find font that supports subscripts
			/*
			 * if (architecture.contains("_8")) label += "\u2088";
			 */
			return label;
		}
	}

	static ApperanceModels apperanceModelsLatex = new ApperanceModels(true);

	static class ApperanceMems extends SeriesApperanceStrategy {
		@Override
		public String labelFor(String architecture, String creationStrategy,
				int level, int mems) {
			String label = (mems + 1) + " ";
			if (mems == 0)
				label += "mem";
			else
				label += "mems";
			return label;
		}
	}

	static class ApperanceStrategies extends SeriesApperanceStrategy {
		@Override
		public String labelFor(String architecture, String creationStrategy,
				int level, int mems) {
			if (creationStrategy.contains("retro_ig"))
				return "RMinOS";
			else if (creationStrategy.contains("retro_interval"))
				return "RMaxS";
			else
				return "NA";
		}
	}

	static ApperanceStrategies apperanceStrategies = new ApperanceStrategies();

	static class ApperanceStrategiesAndModels extends SeriesApperanceStrategy {
		ApperanceStrategies strats = new ApperanceStrategies();

		@Override
		public String labelFor(String architecture, String creationStrategy,
				int level, int mems) {
			return apperanceModelsLatex.labelFor(architecture,
					creationStrategy, level, mems)
					+ "+"
					+ strats.labelFor(architecture, creationStrategy, level,
							mems);
		}
	}

	static ApperanceStrategiesAndModels labelGen = new ApperanceStrategiesAndModels();

	public Experiment_4c_monroe_effectOfTrainingDataLarge(String expName,
			String rootFolder, String dataset) {
		super(expName, rootFolder, dataset);

	}

	public static void createTestsReport(
			Map<String, Map<String, DescriptiveStatistics[][]>> data)
			throws IllegalArgumentException, MathException {

		PrintStream out = System.out;

		String chmmArch = "monroe-middle_chmm-obs^1";
		String ahmemArch = "monroe-middle_ahmem-obs^1_8";
		String[] comparedArchitectures = new String[] { chmmArch, ahmemArch };
		String[] archsPretty = new String[] { "CHMM^1", "AHMEM^1_8" };
		String[] comparedStrategies = new String[] { "retro_interval",
				"retro_ig" };
		int[] levels = new int[] { 0, 1 };
		int[] mems = new int[] { 0, 1, 2 };
		String[] levelNames = new String[] { "O", "E^1" };

		out.println();
		out.println("Body of latex table with accuracy.");
		out.println();

		// List<List<String>> lines = new ArrayList<>();
		// print latex table with results
		int archsNum = comparedArchitectures.length;
		for (int level : levels) {

			// lines.add(line);
			for (int i = 0; i < archsNum; i++) {
				List<String> line = new ArrayList<>();
				// add level O,E
				String prepend = null;
				if (i % archsNum == 0) {
					// prepend =
					// "\\parbox[t]{1mm}{\\multirow{"+archsNum+"}{*}{\\rotatebox[origin=c]{90}{"+levelNames[i/archsNum]+"}}}";
					out.println("\\hline");
					prepend = "\\parbox[t]{1mm}{\\multirow{" + archsNum
							+ "}{*}{$" + levelNames[level] + "$ }}";
				} else
					prepend = "";
				line.add(prepend);

				line.add("$" + archsPretty[i] + "$");
				for (int j = 0; j < comparedStrategies.length; j++) {
					for (int k = 0; k < mems.length; k++) {
						DescriptiveStatistics stat = data.get(
								comparedArchitectures[i]).get(
								comparedStrategies[j])[level][k];
						Long meanRounded = Math.round(stat.getMean() * 100);
						// line.add(meanRounded.toString());

						Long varRounded = Math.round(stat
								.getStandardDeviation() * 100);
						line.add("$" + meanRounded + " \\pm " + varRounded
								+ "$");
					}

				}

				out.println(StringUtils.join(line, " & ")
						+ "\\bigstrut[t] \\\\");
			}
		}

		out.println();
		out.println();
		out.println("Table investigating effect of having n versus n-1 mems.");
		out.println();

		for (String arch : comparedArchitectures) {
			for (String strat : comparedStrategies) {
				out.println("% Comparing effect of more mems in " + arch
						+ " with " + strat + " (p values)");
				out.println("\\hline");
				performTTestsForMemsInOneArch(data.get(arch).get(strat),
						levelNames, arch, strat, out);
			}
		}

		out.println();
		out.println();
		out.println("Table investigating effect of probabilistic models.");
		out.println();

		for (String strat : comparedStrategies) {
			out.println("\\hline");
			out.print(latexMultiRow(
					apperanceStrategies.labelFor("", strat, 0, 0),
					levelNames.length * mems.length, 55));

			for (int i = 0; i < levelNames.length; i++) {
				out.print("& "
						+ latexMultiRow("$" + levelNames[i] + "$", mems.length,
								20));
				// List<Double[]> res = new ArrayList<Double[]>();
				for (int j = 0; j < mems.length; j++) {
					if (j == 0)
						out.print(" & ");
					else
						out.print(" & & ");
					DescriptiveStatistics s1 = data.get(chmmArch).get(strat)[i][j];
					DescriptiveStatistics s2 = data.get(ahmemArch).get(strat)[i][j];
					// res.add(performTTestsAndCohen(s1, s2));
					out.print((j + 1)
							+ " & "
							+ formatLatexTableRowWithTTestAndCohen(Collections
									.singletonList(performTTestsAndCohen(s1, s2))));
					out.println("\\\\");
				}
				out.println("\\hline");
			}
		}

		out.println();
		out.println();
		out.println("Table investigating effect of encoding strategy.");
		out.println();

		for (String model : comparedArchitectures) {
			out.println("\\hline");
			out.print(latexMultiRow(
					"$" + apperanceModelsLatex.labelFor(model, "", 0, 0) + "$",
					levelNames.length * mems.length, 55));

			for (int i = 0; i < levelNames.length; i++) {
				out.print("& "
						+ latexMultiRow("$" + levelNames[i] + "$", mems.length,
								20));
				// List<Double[]> res = new ArrayList<Double[]>();
				for (int j = 0; j < mems.length; j++) {
					if (j == 0)
						out.print(" & ");
					else
						out.print(" & & ");
					DescriptiveStatistics s1 = data.get(model).get(
							comparedStrategies[0])[i][j];
					DescriptiveStatistics s2 = data.get(model).get(
							comparedStrategies[1])[i][j];
					// res.add(performTTestsAndCohen(s1, s2));
					out.print((j + 1)
							+ " & "
							+ formatLatexTableRowWithTTestAndCohen(Collections
									.singletonList(performTTestsAndCohen(s1, s2))));
					out.println("\\\\");
				}
			}
		}

	}

	public static String latexMultiRow(String text, int rows, int points) {
		return "\\multirow{" + rows + "}{*}{\\parbox{" + points + "pt}{" + text
				+ "}}";
	}

	public static double cohensD(DescriptiveStatistics s1,
			DescriptiveStatistics s2) {
		double sPooled = Math
				.sqrt((Math.pow(s1.getStandardDeviation(), 2) + Math.pow(
						s2.getStandardDeviation(), 2)) / 2.0);
		return (s1.getMean() - s2.getMean()) / sPooled;
	}

	static NumberFormat formatterExp = new DecimalFormat("0.##E0");
	static NumberFormat formatterPlain = new DecimalFormat("0.##");
	static NumberFormat formatterCohen = new DecimalFormat("0.000");
	static NumberFormat formatterPValue = new DecimalFormat(".000");

	public static String latexNumberFormat(double x) {
		/*
		 * String num; if (x < 1) { num = formatterExp.format(x); String[] spl =
		 * num.split("E"); num = spl[0] + "\\times 10^{" + spl[1] + "}"; } else
		 * { num = formatterPlain.format(x); }
		 */
		String num = "NA";
		if (x < 0.001)
			num = "< .001";
		else {
			if (x > 0.1)
				num = "> 0.1";
			else 
				num = formatterPValue.format(x);
		}

		return "$" + num + "$";
	}

	public static String latexCohenDFormat(double x) {
		return "$" + formatterCohen.format(x) + "$";
	}

	public static String latexNumberFormatHighlight(double x, double threshold) {
		String ret = "";
		if (x > threshold)
			ret += "\\cellcolor{blue!25}";
		return ret + latexNumberFormat(x);
	}

	public static void performTTestsForMemsInOneArch(
			DescriptiveStatistics[][] data, String[] levelNames, String arch,
			String strat, PrintStream out) throws IllegalArgumentException,
			MathException {

		// out.print("\\parbox[t]{1mm}{\\multirow{" + data.length
		// + "}{*}{$" + labelGen.labelFor(arch, strat, 0, 0) + "$ }}");

		out.print(latexMultiRow("$" + labelGen.labelFor(arch, strat, 0, 0)
				+ "$", data.length, 65));

		// for all levels
		for (int i = 0; i < data.length; i++) {
			out.print("& \t $" + levelNames[i] + "$ \t");
			out.print("& "
					+ formatLatexTableRowWithTTestAndCohen(performTTestsForMemsOnSingleLevelInOneArch(data[i])));

			out.println("\\\\");
		}
	}

	public static String formatLatexTableRowWithTTestAndCohen(
			List<Double[]> list) {
		return StringUtils.join(
				list.stream()
						.map(x -> latexNumberFormatHighlight(x[0], 0.05)
								+ "\t & " + latexCohenDFormat(x[1]))
						.collect(Collectors.toList()), "\t & ");
	}

	public static List<Double[]> performTTestsForMemsOnSingleLevelInOneArch(
			DescriptiveStatistics[] memsData) throws IllegalArgumentException,
			MathException {
		List<Double[]> results = new ArrayList<>();
		for (int memsIx = 0; memsIx < memsData.length - 1; memsIx++) {
			DescriptiveStatistics s1 = memsData[memsIx];
			DescriptiveStatistics s2 = memsData[memsIx + 1];
			results.add(performTTestsAndCohen(s1, s2));
		}
		return results;
	}

	public static Double[] performTTestsAndCohen(DescriptiveStatistics s1,
			DescriptiveStatistics s2) throws IllegalArgumentException,
			MathException {

		double test = TestUtils.pairedTTest(s1.getValues(), s2.getValues());

		// convert two sided to one sided, add Cohen's D
		// Bonferoni correction for 40 tests
		return new Double[] { 40 * test / 2, cohensD(s2, s1) };
	}

	public static void createTrainingDataInfluenceGraph(String expsRootFolder)
			throws Exception {

		File graphsFolder = new File(expsRootFolder, "graphs");
		graphsFolder.mkdirs();

		List<Entry<Integer, Map<String, Map<String, DescriptiveStatistics[][]>>>> list = loadMultiExperimentResultData(expsRootFolder);

		// reports
		TimeStats.reportTimeStats(new File(expsRootFolder, "unseen_split-725"));
		createTestsReport(list.get(0).getValue());
		createTestsReport(list.get(list.size() - 1).getValue());

		String chmmArch = "monroe-middle_chmm-obs^1";
		String ahmemArch = "monroe-middle_ahmem-obs^1_8";

		String[] comparedArchitectures = new String[] { chmmArch, ahmemArch };

		String[] comparedStrategies = new String[] { "retro_ig",
				"retro_interval" };

		int[] levels = new int[] { 0, 1 };

		int[] mems = new int[] { 0, 1, 2 };

		SeriesApperanceStrategy modelComparison = new ApperanceModels();

		// create dataset for graph
		// GraphPaintingDevice device = new PngGraphDevice(graphsFolder, 1000,
		// 500);
		GraphPaintingDevice device = new PdfGraphDevice(graphsFolder, 700, 350);

		// compare CHMM^1 and AHMEM^1_8
		YIntervalSeriesCollection dataset1 = createGraphDataset(list,
				comparedArchitectures, new String[] { "retro_ig" },
				new int[] { 0 }, new int[] { 2 }, modelComparison);

		YIntervalSeriesCollection dataset2 = createGraphDataset(list,
				comparedArchitectures, new String[] { "retro_interval" },
				new int[] { 0 }, new int[] { 2 }, modelComparison);

		// print graphs showing how more mems affect recall
		allMemComparisonGraphs(comparedArchitectures, comparedStrategies,
				levels, mems, list, device);

		allStrategiesComparisonGraphs(comparedArchitectures,
				comparedStrategies, levels, mems, list, device);

		allModelsComparisonGraphs(comparedArchitectures, comparedStrategies,
				levels, mems, list, device);

		
		outputTrainingDataInfluenceGraph("chmmXahmem+retro_ig", dataset1,
				device);

		// showTrainingDataInfluenceGraph(dataset2);

	}

	public static void allMemComparisonGraphs(
			String[] archs,
			String[] strategies,
			int[] levels,
			int[] mems,
			List<Entry<Integer, Map<String, Map<String, DescriptiveStatistics[][]>>>> list,
			GraphPaintingDevice device) throws Exception {
		for (String arch : archs)
			for (String strategy : strategies)
				for (int level : levels)
					memsComparisonGraph(arch, strategy, level, mems, device,
							list);
	}

	public static void allStrategiesComparisonGraphs(
			String[] archs,
			String[] strategies,
			int[] levels,
			int[] mems,
			List<Entry<Integer, Map<String, Map<String, DescriptiveStatistics[][]>>>> list,
			GraphPaintingDevice device) throws Exception {
		for (String arch : archs)
			for (int mem : mems)
				for (int level : levels)
					strategyComparisonGraph(arch, strategies, level, mem,
							device, list);
	}

	public static void allModelsComparisonGraphs(
			String[] archs,
			String[] strategies,
			int[] levels,
			int[] mems,
			List<Entry<Integer, Map<String, Map<String, DescriptiveStatistics[][]>>>> list,
			GraphPaintingDevice device) throws Exception {

		for (String strategy : strategies)
			for (int mem : mems)
				for (int level : levels)
					modelComparisonGraph(archs, strategy, level, mem, device,
							list);
	}

	public static void memsComparisonGraph(
			String arch,
			String strategy,
			int level,
			int[] mems,
			GraphPaintingDevice device,
			List<Entry<Integer, Map<String, Map<String, DescriptiveStatistics[][]>>>> list)
			throws Exception {
		SeriesApperanceStrategy memsComparison = new ApperanceMems();

		// mems effect for ahmem on observations
		YIntervalSeriesCollection datasetMems = createGraphDataset(list,
				new String[] { arch }, new String[] { strategy },
				new int[] { level }, mems, memsComparison);
		outputTrainingDataInfluenceGraph("memsComparisonFor_" + arch + "+"
				+ strategy + "+L" + level, datasetMems, device);

	}

	public static void strategyComparisonGraph(
			String arch,
			String[] strategies,
			int level,
			int mem,
			GraphPaintingDevice device,
			List<Entry<Integer, Map<String, Map<String, DescriptiveStatistics[][]>>>> list)
			throws Exception {

		YIntervalSeriesCollection datasetMems = createGraphDataset(list,
				new String[] { arch }, strategies, new int[] { level },
				new int[] { mem }, new ApperanceStrategies());
		outputTrainingDataInfluenceGraph("strategyComparisonFor_" + arch + "+L"
				+ level + "+M" + (mem + 1), datasetMems, device);

	}

	public static void modelComparisonGraph(
			String[] models,
			String strategy,
			int level,
			int mem,
			GraphPaintingDevice device,
			List<Entry<Integer, Map<String, Map<String, DescriptiveStatistics[][]>>>> list)
			throws Exception {

		YIntervalSeriesCollection datasetMems = createGraphDataset(list,
				models, new String[] { strategy }, new int[] { level },
				new int[] { mem }, new ApperanceModels());
		outputTrainingDataInfluenceGraph("modelComparisonFor_" + strategy
				+ "+L" + level + "+M" + (mem + 1), datasetMems, device);

	}

	public static void main(String[] args) throws Exception {
		int numberOfDaysInOneBatch = 25;

		int testingData = 100;

		//createTrainingDataInfluenceGraph(expsRootFolder);
		
		for (int i = 1; i < 30; i++) {
			int trainingDataNum = i * numberOfDaysInOneBatch;

			Experiment e = new Experiment_4c_monroe_effectOfTrainingData(
					"unseen_split-" + trainingDataNum, expsRootFolder,
					"../datasets/4b_monroe/monroe-middle.txt");
			e.setTestingDataRange(new int[] { 0, testingData });
			e.setTrainingDataRange(new int[] { testingData,
					testingData + trainingDataNum });
			// e.setTrainingDataRange(new int[] {0, trainingDataNum});
			e.perform();
		}
		
		createTrainingDataInfluenceGraph(expsRootFolder);
	}

}
